package cn.lingyangwl.framework.tool.core;

import java.util.concurrent.TimeUnit;

/**
 * 存储单位工具类, 优雅的实现计算机容量Bit Byte KB等互相转换
 * <p>
 * 日常工作中, 偶尔会遇到计算机容量之间的相互转换. 众所周知计算机的基本存储单位有
 * Bit, Byte, KB, MB, GB, TB等. 这里参考 {@link TimeUnit} 实现一套优雅的计算机容量
 * 互相转换工具, 并提供一个支持自动升级容量单位的方法.
 * <p>
 * 此工具类是使用 long 为基本数据类型实现的, 对于低级容量向高级容量转换, 会丢失精度.
 * 若对精度有需求, 将 long 类型改为 double 就可以.
 * 需要注意的是, 使用 double 类型进行计算, 可能导致计算结果不精确, 对计算结果有严格
 * 要求的工程请实现 BigDecimal 版本.
 *
 * @author shenguangyang
 */
public enum StoreUnit {
    BIT {
        public long convert(long source, StoreUnit storeUnit) {
            return storeUnit.toBit(source);
        }

        // 自己对自己转换 直接返回
        public long toBit(long s) {
            return s;
        }

        // 升级则调用升级方法
        public long toByte(long s) {
            return upper(s, C1 / C0);
        }

        public long toKB(long s) {
            return upper(s, C2 / C0);
        }

        public long toMB(long s) {
            return upper(s, C3 / C0);
        }

        public long toGB(long s) {
            return upper(s, C4 / C0);
        }

        public long toTB(long s) {
            return upper(s, C5 / C0);
        }
    },

    BYTE {
        public long convert(long source, StoreUnit storeUnit) {
            return storeUnit.toByte(source);
        }

        // 降级调用降级方法
        public long toBit(long s) {
            return lower(s, C1 / C0);
        }

        // 自己对自己转换 直接返回
        public long toByte(long s) {
            return s;
        }

        // 升级则调用升级方法
        public long toKB(long s) {
            return upper(s, C2 / C1);
        }

        public long toMB(long s) {
            return upper(s, C3 / C1);
        }

        public long toGB(long s) {
            return upper(s, C4 / C1);
        }

        public long toTB(long s) {
            return upper(s, C5 / C1);
        }
    },
    KB {
        public long convert(long source, StoreUnit storeUnit) {
            return storeUnit.toKB(source);
        }

        public long toBit(long s) {
            return lower(s, C2 / C0);
        }

        public long toByte(long s) {
            return lower(s, C2 / C1);
        }

        public long toKB(long s) {
            return s;
        }

        public long toMB(long s) {
            return upper(s, C3 / C2);
        }

        public long toGB(long s) {
            return upper(s, C4 / C2);
        }

        public long toTB(long s) {
            return upper(s, C5 / C2);
        }
    },
    MB {
        public long convert(long source, StoreUnit storeUnit) {
            return storeUnit.toMB(source);
        }

        public long toBit(long s) {
            return lower(s, C3 / C0);
        }

        public long toByte(long s) {
            return lower(s, C3 / C1);
        }

        public long toKB(long s) {
            return lower(s, C3 / C2);
        }

        public long toMB(long s) {
            return s;
        }

        public long toGB(long s) {
            return upper(s, C4 / C3);
        }

        public long toTB(long s) {
            return upper(s, C5 / C3);
        }
    },
    GB {
        public long convert(long source, StoreUnit storeUnit) {
            return storeUnit.toGB(source);
        }

        public long toBit(long s) {
            return lower(s, C4 / C0);
        }

        public long toByte(long s) {
            return lower(s, C4 / C1);
        }

        public long toKB(long s) {
            return lower(s, C4 / C2);
        }

        public long toMB(long s) {
            return lower(s, C4 / C3);
        }

        public long toGB(long s) {
            return s;
        }

        public long toTB(long s) {
            return upper(s, C5 / C4);
        }
    },
    TB {
        public long convert(long source, StoreUnit storeUnit) {
            return storeUnit.toTB(source);
        }

        public long toBit(long s) {
            return lower(s, C5 / C0);
        }

        public long toByte(long s) {
            return lower(s, C5 / C1);
        }

        public long toKB(long s) {
            return lower(s, C5 / C2);
        }

        public long toMB(long s) {
            return lower(s, C5 / C3);
        }

        public long toGB(long s) {
            return lower(s, C5 / C4);
        }

        public long toTB(long s) {
            return s;
        }
    };

    /**
     * 将 待转换单位的大小 转换 为此单位的大小.
     * <p>
     * 从细粒度向粗粒度的转化将会被截断处理, 所以会失去精确性.
     * 例如, 转换 {@code 999} KB 到 MB 的结果是 {@code 0}.
     * 从粗粒度转化为细粒度, 结果可能溢出 {@code Long.MAX_VALUE}
     * 或 {@code Long.MIN_VALUE}
     * <p>
     * For example, to convert 10 KB to MB, use:
     * {@code StoreUnit.MB.convert(10L, StoreUnit.KB)}
     *
     * @param source    storeUnit 对应的实际容量
     * @param storeUnit 被转换的单位
     * @return 转换成后的容量大小
     */
    public long convert(long source, StoreUnit storeUnit) {
        throw new AbstractMethodError();
    }

    /**
     * 容量单位互相转换的公共定义
     */
    public long toBit(long source) {
        throw new AbstractMethodError();
    }

    public long toByte(long source) {
        throw new AbstractMethodError();
    }

    public long toKB(long source) {
        throw new AbstractMethodError();
    }

    public long toMB(long source) {
        throw new AbstractMethodError();
    }

    public long toGB(long source) {
        throw new AbstractMethodError();
    }

    public long toTB(long source) {
        throw new AbstractMethodError();
    }

    // bit
    static final long C0 = 1L;
    // bit to byte
    static final long C1 = C0 << 3;
    // byte to KB
    static final long C2 = C1 << 10;
    // KB to MB
    static final long C3 = C2 << 10;
    // MB to GB
    static final long C4 = C3 << 10;
    // GB to TB
    static final long C5 = C4 << 10;

    static final long MAX = Long.MAX_VALUE;

    /**
     * 自动升级容量
     * <p>
     * 传入任意一类小容量, 自动识别适合使用哪种容量来展示
     * For example, 如果你传入 1024BIT, 那么就会输出 128BYTE
     *
     * @param storeUnit 单位
     * @param source    数据大小
     * @return 返回带有单位的数据
     */
    public static String autoUpper(long source, StoreUnit storeUnit) {
        long convert;
        switch (storeUnit) {
            case BIT:
                if ((convert = BIT.convert(source, storeUnit)) < 8) return convert + BIT.name();
            case BYTE:
                if ((convert = BYTE.convert(source, storeUnit)) < 1024) return convert + BYTE.name();
            case KB:
                if ((convert = KB.convert(source, storeUnit)) < 1024) return convert + KB.name();
            case MB:
                if ((convert = MB.convert(source, storeUnit)) < 1024) return convert + MB.name();
            case GB:
                if ((convert = GB.convert(source, storeUnit)) < 1024) return convert + GB.name();
            case TB:
                if ((convert = TB.convert(source, storeUnit)) < 1024) return convert + TB.name();
            default:
                return source + storeUnit.name();
        }
    }

    /**
     * 降级
     * <p>
     * 将 s 按 m 进行放大, 并检查是否溢出
     *
     * @param target 原值
     * @param m      放大比例
     */
    static long lower(long target, long m) {
        long absoluteOver = MAX / m;
        if (target > absoluteOver) return Long.MAX_VALUE;
        if (target < -absoluteOver) return Long.MIN_VALUE;
        return target * m;
    }

    /**
     * 升级
     */
    static long upper(long s, long m) {
        return s / m;
    }
}
